package per.hnvcam.hedge.algebra.impl;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import per.hnvcam.common.OrderedSet;
import per.hnvcam.common.impl.OrderedSetImpl;
import per.hnvcam.searchext.data.dao.ConfigDAO;
import per.hnvcam.hedge.algebra.HedgeAlgebraDefinition;
import per.hnvcam.searchext.data.model.Config;
import per.hnvcam.hedge.algebra.HedgeConstants;

import java.util.*;

@Service("HedgeAlgebraDefinition")
public class HedgeAlgebraDefinitionImpl implements HedgeAlgebraDefinition, InitializingBean {

   @Autowired
   @Qualifier("ConfigDAO")
   private ConfigDAO configDAO;

   private OrderedSet<String> positiveG;
   private OrderedSet<String> negativeG;
   private OrderedSet<String> positiveH;
   private OrderedSet<String> negativeH;
   private OrderedSet<String> domain;
   private Map<String, Double> functionMeter;
   private double alpha;
   private double beta;
   private int hedgeLayerNumber;

   public HedgeAlgebraDefinitionImpl() {
      this.hedgeLayerNumber = 1;
      positiveG = new OrderedSetImpl<String>();
      negativeG = new OrderedSetImpl<String>();
      positiveH = new OrderedSetImpl<String>();
      negativeH = new OrderedSetImpl<String>();
      functionMeter = new HashMap<String, Double>();
   }

   public OrderedSet<String> getPositiveG() {
      return positiveG;
   }

   public OrderedSet<String> applyHedge(String item) {
      OrderedSet<String> result = new OrderedSetImpl<String>();
      negativeH.setAscendingSort(false);
      for (String h : negativeH) {
         String x = h + " " + item;
         result.add(x);
      }

      positiveH.setAscendingSort(true);
      for (String h : positiveH) {
         String x = h + " " + item;
         result.add(x);
      }
      return result;
   }

   public void setFunctionMeterValue(String term, double functionMeterValue) {
      if (!positiveG.contains(term) && !negativeG.contains(term) && positiveH.contains(term) && negativeH.contains(term)) {
         throw new IllegalArgumentException("The specified term \"" + term + "\" is invalid.");
      }
      functionMeter.put(term, functionMeterValue);
   }

   private double getFunctionMeterValueOfBasicTerm(String term) {
      Double value = functionMeter.get(term);
      if (value == null) {
         return 0.0;
      }
      return value.doubleValue();
   }

   public double getFunctionMeterValue(String item) {
      double value = 1.0;
      String [] items = item.split(" ");
      for (String s : items) {
         value = value * getFunctionMeterValueOfBasicTerm(s);
      }
      return value;
   }

   public boolean checkForConsistency() {
      if (sumFunctionMeterValue(positiveG) + sumFunctionMeterValue(negativeG) != 1.0) {
         throw new IllegalStateException("The total of function meter value of Generators is not equal to 1.");
      }

      beta = sumFunctionMeterValue(positiveH);
      alpha = sumFunctionMeterValue(negativeH);

      if (alpha + beta != 1.0) {
         throw new IllegalStateException("The total of alpha and beta is not equal to 1.");
      }

      return true;
   }



   private double sumFunctionMeterValue(OrderedSet<String> set) {
      double result = 0.0;
      for (String term : set) {
         Double value = functionMeter.get(term);
         if (value != null) {
            result += value.doubleValue();
         }
      }
      return result;
   }

   private int compareWord(String word1, String word2) {
      int posG1 = positiveG.indexOf(word1);
      int posG2 = positiveG.indexOf(word2);
      int posN1 = negativeG.indexOf(word1);
      int posN2 = negativeG.indexOf(word2);
      int compared = posG1 - posG2;
      int compared2 = posN1 - posN2;

      if (compared != 0 || compared2 != 0) {
         return compared != 0 ? compared : compared2;
      }
      else {
         if ((posG1 >= 0 && posG2 >= 0) || (posN1 >= 0 && posN2 >= 0)) {
            return 0;
         }
         posG1 = positiveH.indexOf(word1);
         posG2 = positiveH.indexOf(word2);
         posN1 = negativeH.indexOf(word1);
         posN2 = negativeH.indexOf(word2);
         compared = posG1 - posG2;
         compared2 = posN1 - posN2;
         if (compared != 0 || compared2 != 0) {
            return compared != 0 ? compared : compared2;
         }
         else {
            if ((posG1 >= 0 && posG2 >= 0) || (posN1 >= 0 && posN2 >= 0)) {
               return 0;
            }
         }
      }
      throw new IllegalArgumentException("Could not compare these items \"" + word1 + "\", \"" + word2 + "\"");
   }

   public int compareCompound(String comp1, String comp2) {
      String[] listWord1 = comp1.split(" ");
      String[] listWord2 = comp2.split(" ");
      int length1 = listWord1.length - 1;
      int length2 = listWord2.length - 1;
      int sign1 = getSignOfGenerator(listWord1[length1]);
      int sign2 = getSignOfGenerator(listWord2[length2]);
      if (sign1 < sign2) {
         return -1;
      } else if (sign1 > sign2) {
         return 1;
      } else {
         int result = compareWord(listWord1[length1], listWord2[length2]);
         if (result != 0) {
            return result;
         }
         length1--;
         length2--;
         while (length1 >= 0 && length2 >= 0) {
            sign1 = sign1 * getSignOfHedge(listWord1[length1], listWord1[length1 + 1]);
            sign2 = sign2 * getSignOfHedge(listWord2[length2], listWord2[length2 + 1]);
            if (sign1 < sign2) {
               return -1;
            } else if (sign1 > sign2) {
               return 1;
            } else {
               result = compareWord(listWord1[length1], listWord2[length2]);
               if (result != 0) {
                  return result * sign1;
               }
            }
            length1--;
            length2--;
         }
      }
      if (length1 >= 0) {
         return sign1 * getSignOfHedge(listWord1[length1], listWord1[length1 + 1]);
      }
      else if (length2 >= 0) {
         return sign2 * getSignOfHedge(listWord2[length2], listWord2[length2 + 1]);
      }
      else {
         return 0;
      }
   }

   private int getSignOfGenerator(String item) {
      if (positiveG.contains(item)) {
         return 1;
      }
      else if (negativeG.contains(item)) {
         return -1;
      }
      else {
         throw new IllegalArgumentException("Could not getSign of item \"" + item + "\"");
      }
   }

   private int getSignOfHedge(String hedge, String preItem) {
      if (positiveH.contains(hedge)) {
         if (positiveH.contains(preItem) || positiveG.contains(preItem) || negativeG.contains(preItem)) {
         return 1;
         } else if (negativeH.contains(preItem)) {
            return -1;
         }
      } if (negativeH.contains(hedge)) {
         if (positiveH.contains(preItem) || positiveG.contains(preItem) || negativeG.contains(preItem)) {
            return -1;
         } else if (negativeH.contains(preItem)) {
            return 1;
         }
      }
      throw new IllegalArgumentException("Could not getSign of hedge \"" + hedge + "\" compared to \"" + preItem + "\"");
   }

   public int getSign(String compound) {
      int sign = 1;
      String [] terms = compound.split(" ");
      for (int i = 0; i < terms.length - 1; i++) {
         sign = sign * getSignOfHedge(terms[i], terms[i+1]);
      }
      return sign * getSignOfGenerator(terms[terms.length - 1]);
   }

   public OrderedSet<String> generateDomain() {
      OrderedSet<String> result = new OrderedSetImpl<String>(new HedgeComparator());
      List<String> newItems = new ArrayList<String>();
      result.addAll(positiveG);
      result.addAll(negativeG);
      newItems.addAll(positiveG);
      newItems.addAll(negativeG);
      int numberOfLayer = hedgeLayerNumber;
      while (numberOfLayer > 0) {
         List<String> tmpNew = new ArrayList<String>();
         for (String newItem : newItems) {
            tmpNew.addAll(applyHedge(newItem));
         }
         result.addAll(tmpNew);
         newItems = tmpNew;
         numberOfLayer--;
      }
      return result;
   }

   public int getHedgeLayerNumber() {
      return hedgeLayerNumber;
   }

   public void setHedgeLayerNumber(int hedgeLayerNumber) {
      this.hedgeLayerNumber = hedgeLayerNumber;
   }

   public void setPositiveG(OrderedSet<String> positiveG) {
      this.positiveG = positiveG;
   }

   public OrderedSet<String> getNegativeG() {
      return negativeG;
   }

   public void setNegativeG(OrderedSet<String> negativeG) {
      this.negativeG = negativeG;
   }

   public OrderedSet<String> getPositiveH() {
      return positiveH;
   }

   public void setPositiveH(OrderedSet<String> positiveH) {
      this.positiveH = positiveH;
   }

   public OrderedSet<String> getNegativeH() {
      return negativeH;
   }

   public void setNegativeH(OrderedSet<String> negativeH) {
      this.negativeH = negativeH;
   }

   public double getAlpha() {
      return alpha;
   }

   public double getBeta() {
      return beta;
   }

   public void refresh() {
      alpha = sumFunctionMeterValue(negativeH);
      beta = sumFunctionMeterValue(positiveH);
   }

   public OrderedSet<String> getDomain() {
      if (domain == null) {
         domain = generateDomain();
      }
      return domain;
   }

   @Override
   public void afterPropertiesSet() throws Exception {
      positiveG.add(HedgeConstants.MANY);
      negativeG.add(HedgeConstants.FEW);
      positiveH.add(HedgeConstants.VERY);
      negativeH.add(HedgeConstants.LITTLE);
      getFMValue(HedgeConstants.MANY);
      getFMValue(HedgeConstants.FEW);
      getFMValue(HedgeConstants.VERY);
      getFMValue(HedgeConstants.LITTLE);
      refresh();
   }

   private void getFMValue(String item) {
      Config config = configDAO.findByProperty(item);
      if (config != null && config.getValue() != null) {
         setFunctionMeterValue(item, Double.parseDouble(config.getValue()));
      }
   }

   class HedgeComparator implements Comparator<String> {
      @Override
      public int compare(String s1, String s2) {
         return compareCompound(s1, s2);
      }
   }
}
